カスタムリソースの Lambda でAssumeRoleして別アカウントでS3バケットを作成する
こんにちは、森田です。
こちらの記事では、タイトルの通り別アカウントからのCloudFormationでS3バケットの作成を行います。
カスタムリソースの Lambdaに対して権限委任を行い、スタック作成・削除に伴いS3バケットの作成・削除をBoto3を用いて行います。
やりたいこと
AWSアカウントA のCloudFormationから AWSアカウントB でS3バケットの作成を行います。
上記を実現させるために、AWSアカウントA のロールに権限委任を行います。
構成は以下のようになります。
上記を実現させるため、
- カスタムリソースのLambdaのロール作成
- AWSアカウントAへ権限委任するロール作成
- カスタムリソースのLambdaからS3を作成する
のCloudFormationテンプレートを作成します。
やってみる
CloudFormationテンプレートの作成
カスタムリソースのLambdaのロール作成
custom_resource_role.yml (クリックして展開)
AWSTemplateFormatVersion: "2010-09-09" Parameters: RoleName: Type: String Description: RoleName Default: "AssumeRole-Lambda" Resources: LambdaExecutionRole: Type: AWS::IAM::Role Properties: RoleName: !Ref RoleName AssumeRolePolicyDocument: Version: "2012-10-17" Statement: - Effect: Allow Principal: Service: - lambda.amazonaws.com Action: - sts:AssumeRole Path: / Policies: - PolicyName: !Sub ${RoleName}-policy PolicyDocument: Version: "2012-10-17" Statement: - Effect: Allow Action: - sts:AssumeRole Resource: "*" Outputs: SwitchLambdaRole: Value: !GetAtt LambdaExecutionRole.Arn Export: Name: SwitchLambdaRoleARN
AWSアカウントAへ権限委任するロール作成
assume_role.yml (クリックして展開)
AWSTemplateFormatVersion: "2010-09-09" Parameters: RoleName: Type: String Description: RoleName Default: "AssumeRole-Lambda" AccountNumber: Type: String Description: External AWS Account ID Resources: LambdaExecutionRole: Type: AWS::IAM::Role Properties: RoleName: !Ref RoleName AssumeRolePolicyDocument: Version: "2012-10-17" Statement: - Effect: Allow Principal: AWS: - !Sub arn:aws:iam::${AccountNumber}:role/${RoleName} Action: - sts:AssumeRole Path: / Policies: - PolicyName: !Sub ${RoleName}-policy PolicyDocument: Version: "2012-10-17" Statement: - Effect: Allow Action: - s3:CreateBucket - s3:DeleteBucket Resource: "*"
カスタムリソースのLambdaからS3を作成する
cfn.yml (クリックして展開)
AWSTemplateFormatVersion: "2010-09-09" Parameters: BucketName: Type: String Description: Create Bucket Name RoleName: Type: String Description: External AWS Account Role Default: "AssumeRole-Lambda" AccountNumber: Type: String Description: External AWS Account ID Resources: SwitchHandler: Type: Custom::SwitchHandler Properties: ServiceToken: !GetAtt "LambdaFunction.Arn" LambdaFunction: Type: AWS::Lambda::Function Properties: FunctionName : !Join ['-', [!Sub '${AWS::StackName}-Lambda', !Select [0, !Split ['-', !Select [2, !Split ['/', !Ref 'AWS::StackId' ]]]]]] Role: !ImportValue SwitchLambdaRoleARN Runtime: "python3.8" Handler: index.lambda_handler Timeout: "300" Environment: Variables: BucketName : !Ref BucketName RoleName: !Ref RoleName AccountNumber: !Ref AccountNumber Code: ZipFile: | import cfnresponse import sys import os import boto3 import time from boto3.session import Session def assume_role(account_number, role_name): sts_client = boto3.client('sts') my_info = sts_client.get_caller_identity() partition = my_info['Arn'].split(":")[1] role_arn = 'arn:{}:iam::{}:role/{}'.format(partition, account_number, role_name) response = sts_client.assume_role( RoleArn=role_arn, RoleSessionName='Session-CFn-User', ) session = boto3.Session( aws_access_key_id=response['Credentials']['AccessKeyId'], aws_secret_access_key=response['Credentials']['SecretAccessKey'], aws_session_token=response['Credentials']['SessionToken'] ) return session def create_bucket(session, bucket_name): s3_client = session.client("s3") s3_client.create_bucket(Bucket=bucket_name, CreateBucketConfiguration={'LocationConstraint': 'ap-northeast-1'}) def delete_bucket(session, bucket_name): s3_client = session.client("s3") s3_client.delete_bucket(Bucket=bucket_name, CreateBucketConfiguration={'LocationConstraint': 'ap-northeast-1'}) def lambda_handler(event, context): if 'RequestType' not in event: return "Overwrite" if event['RequestType'] == 'Create': bucket_name = os.getenv('BucketName') account_number = os.getenv('AccountNumber') role_name = os.getenv('RoleName') session = assume_role(account_number, role_name) create_bucket(session, bucket_name) cfnresponse.send(event, context, cfnresponse.SUCCESS, {'Response': 'Success'}) if event['RequestType'] == 'Delete': bucket_name = os.getenv('BucketName') account_number = os.getenv('AccountNumber') role_name = os.getenv('RoleName') session = assume_role(account_number, role_name) delete_bucket(session, bucket_name) cfnresponse.send(event, context, cfnresponse.SUCCESS, {'Response': 'Success'}) if event['RequestType'] == 'Update': print('Update') cfnresponse.send(event, context, cfnresponse.SUCCESS, {'Response': 'Success'})
CloudFormationの実行・スタック作成
まずは、以下のようにカスタムリソースのLambdaのロール作成の CloudFormation を AWSアカウントA で行います。
続いて、AWSアカウントBでAWSアカウントAへ権限委任するロール作成の CloudFormation を実行します。
そして、最後に AWSアカウントA でCloudFormationを実行し、カスタムリソースのLambdaからS3を作成を行います。
スタック作成後、アカウントBで作成したS3バケットが確認できます。
S3バケットの削除も確認するため、スタックの削除を行います。
スタック削除後、アカウントBからS3バケットが削除されていることが確認できます。
最後に
今回は、CloudFormationのカスタムリソースで別のアカウントのリソース(S3バケット)の操作を行いました。
1つ2つくらいのアカウントでのスタック作成であれば、スイッチロールをしての操作とかでも良いですが、それ以上のアカウントでスタックを作成する場合にはロールの切り替えは少々面倒です。
今回のように事前にロールを作成しておき、カスタムリソースでAssumeRoleするとロール切り替えがなくなるので、作業の簡略化ができそうです。